{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Create a Question Answering (QA) System in Under 20 Minutes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook demonstrates how to create a Question Answering (QA) webservice in under 20 minutes. We use Azure Machine Learning ([AzureML](https://azure.microsoft.com/en-us/services/machine-learning-service/)) Service to deploy a pre-trained [AllenNLP model](https://allennlp.org/models\n",
    "), [BiDAF](https://www.semanticscholar.org/paper/Bidirectional-Attention-Flow-for-Machine-Seo-Kembhavi/007ab5528b3bd310a80d553cccad4b78dc496b02\n",
    "), using Azure Container Instances ([ACI](https://azure.microsoft.com/en-us/services/container-instances/))."
   ]
  },
    {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/nlp/examples/question_answering/bidaf_quickstart.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Table of Contents\n",
    "\n",
    "1. [Deploy Model](#1.-Deploy-Model)\n",
    "    - [1.1 Link to or Create a Workspace](#1.1-Link-to-or-Create-a-Workspace)\n",
    "    - [1.2 Register BiDAF model for Deployment](#1.2-Register-BiDAF-model-for-Deployment)  \n",
    "    - [1.3 Create Scoring Script](#1.3-Create-Scoring-Script)  \n",
    "    - [1.4 Create a YAML File for the Environment](#1.4-Create-a-YAML-File-for-the-Environment)  \n",
    "    - [1.5 Image Creation](#1.5-Image-Creation)\n",
    "    - [1.6 Deploy the Image as a Web Service to Azure Container Instance](#1.6-Deploy-the-Image-as-a-Web-Service-to-Azure-Container-Instance)\n",
    "    \n",
    "2. [Test Deployed Webservice](#2.-Test-Deployed-Webservice)\n",
    "    - [2.1 Real-time Scoring](#2.1-Real-time-Scoring)\n",
    "    - [2.2 Batch Scoring](#2.2-Batch-Scoring)  \n",
    "    \n",
    "3. [Conclusion](#Conclusion)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "sys.path.append(\"../../\")\n",
    "import json\n",
    "import urllib\n",
    "import scrapbook as sb\n",
    "\n",
    "#import utils\n",
    "from utils_nlp.common.timer import Timer\n",
    "from utils_nlp.azureml import azureml_utils\n",
    "\n",
    "from azureml.core.webservice import AciWebservice, Webservice\n",
    "from azureml.core.image import ContainerImage\n",
    "from azureml.core.conda_dependencies import CondaDependencies\n",
    "from azureml.core.model import Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": [
     "parameters"
    ]
   },
   "outputs": [],
   "source": [
    "CPU_CORES = 1\n",
    "MEMORY_GB = 8\n",
    "DEPLOYMENT_PYTHON_VERSION = '3.6.8'\n",
    "DEPLOYMENT_CONDA_PACKAGES = ['jsonnet','cmake','regex','pytorch','torchvision']\n",
    "DEPLOYMENT_PIP_PACKAGES = ['allennlp==0.8.4','azureml-sdk==1.0.48']\n",
    "CONTAINER_TAGS = {'area': \"nlp\", 'type': \"question-answering BiDAF\"}\n",
    "MODEL_TAGS = {\"bidaf\": \"demo\"}\n",
    "config_path = (\n",
    "    \"./.azureml\"\n",
    ")  # Path to the directory containing config.json with azureml credentials\n",
    "\n",
    "webservice_name = \"aci-bidaf-service\" #name for webservice; must be unique within your workspace\n",
    "\n",
    "# Azure resources\n",
    "subscription_id = \"YOUR_SUBSCRIPTION_ID\"\n",
    "resource_group = \"YOUR_RESOURCE_GROUP_NAME\"  \n",
    "workspace_name = \"YOUR_WORKSPACE_NAME\"  \n",
    "workspace_region = \"YOUR_WORKSPACE_REGION\" #Possible values eastus, eastus2 and so on."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Deploy Model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1 Link to or Create a Workspace"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following cell looks to set up the connection to your [Azure Machine Learning service Workspace](https://docs.microsoft.com/azure/machine-learning/service/concept-azure-machine-learning-architecture#workspace). You can choose to connect to an existing workspace or create a new one. \n",
    "\n",
    "**To access an existing workspace:**\n",
    "1. If you have a `config.json` file, you do not need to provide the workspace information; you will only need to update the `config_path` variable that is defined above which contains the file.\n",
    "2. Otherwise, you will need to supply the following:\n",
    "    * The name of your workspace\n",
    "    * Your subscription id\n",
    "    * The resource group name\n",
    "\n",
    "**To create a new workspace:**\n",
    "\n",
    "Set the following information:\n",
    "* A name for your workspace\n",
    "* Your subscription id\n",
    "* The resource group name\n",
    "* [Azure region](https://azure.microsoft.com/en-us/global-infrastructure/regions/) to create the workspace in, such as `eastus2`. \n",
    "\n",
    "This will automatically create a new resource group for you in the region provided if a resource group with the name given does not already exist. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "ws = azureml_utils.get_or_create_workspace(\n",
    "    config_path=config_path,\n",
    "    subscription_id=subscription_id,\n",
    "    resource_group=resource_group,\n",
    "    workspace_name=workspace_name,\n",
    "    workspace_region=workspace_region,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Workspace name: ' + ws.name, \n",
    "      'Azure region: ' + ws.location, \n",
    "      'Subscription id: ' + ws.subscription_id, \n",
    "      'Resource group: ' + ws.resource_group, sep='\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 Register BiDAF model for Deployment"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This step downloads the pre-trained [AllenNLP](https://allennlp.org/models) pretrained model and registers the model in our Workspace. The pre-trained AllenNLP model we use is called Bidirectional Attention Flow for Machine Comprehension ([BiDAF](https://www.semanticscholar.org/paper/Bidirectional-Attention-Flow-for-Machine-Seo-Kembhavi/007ab5528b3bd310a80d553cccad4b78dc496b02\n",
    ")) It achieved state-of-the-art performance on the [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/) dataset in 2017 and is a well-respected, performant baseline for QA. AllenNLP's pre-trained BIDAF model is trained on the SQuAD training set and achieves an EM score of 68.3 on the SQuAD development set. See the [BIDAF deep dive notebook](https://github.com/microsoft/nlp-recipes/examples/question_answering/bidaf_deep_dive.ipynb\n",
    ") for more information on this algorithm and AllenNLP implementation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "x config.json\n",
      "x vocabulary/\n",
      "x vocabulary/non_padded_namespaces.txt\n",
      "x vocabulary/tokens.txt\n",
      "x weights.th\n"
     ]
    }
   ],
   "source": [
    "bidaf_model_url = 'https://s3-us-west-2.amazonaws.com/allennlp/models/bidaf-model-2017.09.15-charpad.tar.gz'\n",
    "urllib.request.urlretrieve(bidaf_model_url, filename=\"bidaf.tar.gz\")\n",
    "!tar xvzf bidaf.tar.gz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Registering a model means registering one or more files that make up a model (in our case, we register all the files contained in the downloaded .tar.gz file). Here we demonstrate how to register a model using the AzureML SDK, but see the [model registration](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-deploy-and-where#registermodel\n",
    ") documentation for other registration methods.\n",
    "\n",
    "\n",
    "**Note**: If you have already registered the model, you need not re-register it. Rather, just retrieve the pre-existing model in your Workspace with `bidaf_model = Model(ws, name='bidaf')`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Registering model bidaf\n"
     ]
    }
   ],
   "source": [
    "bidaf_model = Model.register(workspace = ws,\n",
    "                       model_path =\"bidaf.tar.gz\",\n",
    "                       model_name = \"bidaf\",\n",
    "                       tags = MODEL_TAGS,\n",
    "                       description = \"BiDAF Pretrained Model\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 Create Scoring Script"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this section we show an example of an entry script, score.py, which is called from the deployed webservice. The script must contain:\n",
    "\n",
    "1. init() - This function loads the model in a global object.  \n",
    "2. run() - This function is used for model prediction. The inputs and outputs to run() typically use JSON for serialization and deserilization. \n",
    "\n",
    "Our scoring script allows for both real-time and batch prediction. Each observation is a dictionary with two keys: _question_ and _passage_. With batch prediction we pass in a list of observations and use AllenNLPs `predict_batch_json()` method. For real-time prediction we pass in a single observation and use AllenNLPs `predict()` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting score.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile score.py\n",
    "import json\n",
    "from allennlp.predictors import Predictor\n",
    "from azureml.core.model import Model\n",
    "\n",
    "def init():\n",
    "    global model\n",
    "    bidaf_dir_path = Model.get_model_path('bidaf')\n",
    "    model = Predictor.from_path(bidaf_dir_path)\n",
    "\n",
    "def run(rawdata):\n",
    "    try:\n",
    "        data = json.loads(rawdata)\n",
    "        \n",
    "        # if one question-passage pair was passed\n",
    "        if type(data) == dict:\n",
    "            passage = data['passage']\n",
    "            question = data['question']\n",
    "            result = model.predict(question, passage)[\"best_span_str\"]\n",
    "        \n",
    "        # if multiple question-passage pairs were passed\n",
    "        elif type(data) == list:\n",
    "            result = model.predict_batch_json(data)\n",
    "            result = [i[\"best_span_str\"] for i in result]\n",
    "\n",
    "    except Exception as e:\n",
    "        result = str(e)\n",
    "        return json.dumps({\"error\": result})\n",
    "    return json.dumps({\"result\":result})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.4 Create a YAML File for the Environment "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. The following cells create a file, bidafenv.yml, which specifies the dependencies from the run."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'bidafenv.yml'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "myenv = CondaDependencies.create(conda_packages= DEPLOYMENT_CONDA_PACKAGES,\n",
    "                                 pip_packages= DEPLOYMENT_PIP_PACKAGES, \n",
    "                                 python_version = DEPLOYMENT_PYTHON_VERSION)\n",
    "myenv.add_channel('conda-forge')\n",
    "myenv.add_channel('pytorch')\n",
    "\n",
    "conda_env_file_name = 'bidafenv.yml'\n",
    "myenv.save_to_file('.', conda_env_file_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.5 Image Creation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this step we create a container image which is wrapper containing the entry script, yaml file with package dependencies and the model. The created image is then deployed as a webservice in the next step. This step can take up to 10 minutes and even longer if the model is large."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating image\n",
      "Running.......................................................................................................................................\n",
      "Succeeded\n",
      "Image creation operation finished for image bidaf-image:36, operation \"Succeeded\"\n"
     ]
    }
   ],
   "source": [
    "image_config = ContainerImage.image_configuration(execution_script = \"score.py\",\n",
    "                                                  runtime = \"python\",\n",
    "                                                  conda_file = conda_env_file_name,\n",
    "                                                  description = \"Image with BiDAF model\",\n",
    "                                                  tags = CONTAINER_TAGS)\n",
    "\n",
    "image = ContainerImage.create(name = \"bidaf-image\",\n",
    "                              models = [bidaf_model],\n",
    "                              image_config = image_config,\n",
    "                              workspace = ws)\n",
    "\n",
    "image.wait_for_creation(show_output = True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If the above step fails, then use the below command to see logs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(image.image_build_log_uri)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.6 Deploy the Image as a Web Service to Azure Container Instance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Azure Container Instances are mostly used for deploying your models as a web service if one or more of the following conditions are true:  \n",
    "1. You need to quickly deploy and validate your model.\n",
    "2. You are testing a model that is under development.  \n",
    "\n",
    "\n",
    "To set them up properly, we need to indicate the number of CPU cores and the amount of memory we want to allocate to our web service."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Set the web service configuration\n",
    "aci_config = AciWebservice.deploy_configuration(cpu_cores = CPU_CORES, \n",
    "                                               memory_gb = MEMORY_GB)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The final step to deploying our web service is to call WebService.deploy_from_image(). This function uses the Docker image and the deployment configuration we created above to perform the following:  \n",
    "1. Deploy the docker image to an Azure Container Instance\n",
    "2. Call the init() function in our scoring file\n",
    "3. Provide an HTTP endpoint for scoring calls  \n",
    "\n",
    "The deploy_from_image method requires the following parameters:\n",
    "1. workspace: the workspace containing the service\n",
    "2. name: a unique name used to identify the service in the workspace\n",
    "3. image: a docker image object that contains the environment needed for scoring/inference\n",
    "4. deployment_config: a configuration object describing the compute type\n",
    "\n",
    "**Note**: The web service creation can take a few minutes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating service\n",
      "Running.............................................\n",
      "SucceededACI service creation operation finished, operation \"Succeeded\"\n",
      "Healthy\n"
     ]
    }
   ],
   "source": [
    "# deploy image as web service\n",
    "aci_service = Webservice.deploy_from_image(workspace = ws, \n",
    "                                           name = webservice_name,\n",
    "                                           image = image,\n",
    "                                           deployment_config = aci_config)\n",
    "\n",
    "aci_service.wait_for_deployment(show_output = True)\n",
    "print(aci_service.state)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fetch logs to debug in case of failures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(aci_service.get_logs())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to reuse an existing service versus creating a new one, call the webservice with the name of the service. You can look up all the deployed webservices under deployment in the Azure Portal. Below is an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "# aci_service = Webservice(workspace=ws, name='<<serive-name>>')\n",
    "\n",
    "# to use the webservice\n",
    "# aci_service.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Conclusion**: Now we have a deployed webservice and deploying the model took less than 20 minutes!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Test Deployed Webservice"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Depending on the needs of our QA system, we can either do real-time or batch scoring. We show an example of both types of scoring below using the following example [passage](https://www.semanticscholar.org/paper/Bidirectional-Attention-Flow-for-Machine-Seo-Kembhavi/007ab5528b3bd310a80d553cccad4b78dc496b02) and questions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "passage = \"Machine Comprehension (MC), answering questions about a given context, \\\n",
    "requires modeling complex interactions between the context and the query. Recently,\\\n",
    "attention mechanisms have been successfully extended to MC. Typically these mechanisms\\\n",
    "use attention to summarize the query and context into a single vector, couple \\\n",
    "attentions temporally, and often form a uni-directional attention. In this paper \\\n",
    "we introduce the Bi-Directional Attention Flow (BIDAF) network, a multi-stage \\\n",
    "hierarchical process that represents the context at different levels of granularity \\\n",
    "and uses a bi-directional attention flow mechanism to achieve a query-aware context \\\n",
    "representation without early summarization. Our experimental evaluations show that \\\n",
    "our model achieves the state-of-the-art results in Stanford QA (SQuAD) and\\\n",
    "CNN/DailyMail Cloze Test datasets.\"\n",
    "\n",
    "question1 = \"What is BIDAF?\"\n",
    "question2 = \"What datasets does BIDAF achieve state-of-the-art results on?\"\n",
    "question3 = \"What do attention mechanisms do?\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 Real-time Scoring"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We prepare data for predicting answers for one passage-question pair by creating a dictionary with _question_ and _passage_ keys"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = {\"passage\": passage, \"question\":question1}\n",
    "data = json.dumps(data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Time elapsed: 0.5916\n",
      "Answer: Bi-Directional Attention Flow\n"
     ]
    }
   ],
   "source": [
    "with Timer() as t:\n",
    "    score = aci_service.run(input_data=data)\n",
    "    t.stop()\n",
    "    print(\"Time elapsed: {}\".format(t))\n",
    "    \n",
    "result = json.loads(score)\n",
    "try:\n",
    "    output = result[\"result\"]\n",
    "    sb.glue(\"answer\", output)\n",
    "    print(\"Answer:\", output)\n",
    "except:\n",
    "    print(result[\"error\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We see that the model responded to the question \"What is BiDAF?\" with \"Bi-Directional Attention Flow\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 Batch Scoring\n",
    "\n",
    "We prepare the data for batch scoring by creating a list of dictionaries with _passage_ and _question_ keys."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_multiple = [{\"passage\": passage, \"question\":i} for i in [question1, question2, question3]]\n",
    "data_multiple = json.dumps(data_multiple)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Time elapsed: 0.5267\n",
      "['Bi-Directional Attention Flow', 'Stanford QA (SQuAD) andCNN/DailyMail Cloze Test', 'have been successfully extended to MC']\n"
     ]
    }
   ],
   "source": [
    "with Timer() as t:\n",
    "    score = aci_service.run(input_data=data_multiple)\n",
    "    t.stop()\n",
    "    print(\"Time elapsed: {}\".format(t))\n",
    "    \n",
    "result = json.loads(score)\n",
    "try:\n",
    "    output = result[\"result\"]\n",
    "    print(output)\n",
    "except:\n",
    "    print(result[\"error\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We see that the model responded to the question \"What is BiDAF?\" with \"Bi-Directional Attention Flow\", the question \"What datasets does BIDAF achieve state-of-the-art results on?\" with \"Stanford QA (SQuAD) and CNN/DailyMail Cloze Test\", and the question \"What do attention mechanisms do?\" with \"summarize the query and context into a single vector, couple attentions temporally, and often form a uni-directional attention\". All these answers make sense given the passage and demonstrate that the AllenNLP pre-trained model is a good model for a deployed QA system. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook demonstrated how to produce a fast QA service in under 20 minutes using Azure Container Instances (ACI). We deployed a popular pre-trained model, BiDAF, provided by AllenNLP, which was state-of-the-art in 2017 and performs well on our example queries. "
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python (nlp_gpu)",
   "language": "python",
   "name": "nlp_gpu"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
